package cn.agrael.struts.plugin.ejb3;

import static cn.agrael.struts.plugin.ejb3.StrutsEJBPluginConstant.ENC_PATH;
import static cn.agrael.struts.plugin.ejb3.StrutsEJBPluginConstant.JNDI_PATH_DELIMITER;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

import javax.annotation.Resource;
import javax.ejb.EJB;
import javax.ejb.EJBException;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

import cn.agrael.util.ObjectUtils;
import cn.agrael.util.StringUtils;

/**
 * 应用服务器类的骨干实现，继承该类可以简化实现 {@link ApplicationServer}。
 * 
 * @author <a href="mailto:agraellee@gmail.com">Agrael·Lee</a>
 * 
 */
public abstract class AbstractApplicationServer implements ApplicationServer {

	public Object fieldEJBAnnotationLookup(Class<?> clazz, FieldInjectAnnotationWrapper fieldInjectAnnotationWrapper) throws Exception {
		Field field = fieldInjectAnnotationWrapper.getField();
		Class<?> fieldType = field.getType();
		return lookup(clazz, fieldType, field.getName(), fieldInjectAnnotationWrapper,
				(EJB) fieldInjectAnnotationWrapper.getInjectAnnotation());
	}

	public Object methodEJBAnnotationLookup(Class<?> clazz, MethodInjectAnnotationWrapper methodInjectAnnotationWrapper) throws Exception {
		Method method = methodInjectAnnotationWrapper.getMethod();
		String fieldName = parseFieldName(method.getName());
		Class<?>[] parameterClasses = method.getParameterTypes();
		if (parameterClasses.length != 1) {
			throw new EJBException(method.getName() + " 不是写入方法。");
		}
		return lookup(clazz, parameterClasses[0], fieldName, methodInjectAnnotationWrapper,
				(EJB) methodInjectAnnotationWrapper.getInjectAnnotation());
	}

	/**
	 * 根据 ejb 注解查找并返回对应的对象，并将查找正确对象的 jndi路径 调用
	 * {@link InjectAnnotationWrapper#setJndiPath(String)} 放入
	 * injectAnnotationWrapper 中缓存。
	 * 
	 * @param clazz 注解属于 字段/方法 的对象的 Class 对象。
	 * @param ejbType 注解注入类型的 Class 对象,如果是字段注入则为字段的 Class ，如果为方法注入，则为写入方法的参数的
	 *            Class。
	 * @param name 注解注入 字段/方法 的参数名，如果为字段注入，则为字段名，如果为方法注入，则为写入方法的参数名。
	 * @param injectAnnotationWrapper 注入注解包装器。
	 * @param ejb EJB注解。
	 * @return ejb 注解所对应的对象。
	 * @throws Exception 发生任何异常时。
	 */
	protected Object lookup(Class<?> clazz, Class<?> ejbType, String name, InjectAnnotationWrapper injectAnnotationWrapper, EJB ejb)
			throws Exception {
		StringBuilder jndiNameBuilder = new StringBuilder();
		StringBuilder error = new StringBuilder();
		Context context = null;
		try {
			context = new InitialContext();
			if (!"".equals(ejb.mappedName())) {
				buildEJBMappedNameJndiPath(clazz, jndiNameBuilder, ejbType, ejb);
				try {
					return lookup(jndiNameBuilder, injectAnnotationWrapper, ejbType, context);
				} catch (NamingException e) {
					error.append("[mappedName = ").append(jndiNameBuilder).append("]");
					jndiNameBuilder.delete(0, jndiNameBuilder.length());
				}
			}
			if (!"".equals(ejb.beanName())) {
				// 使用的是 beanName
				buildEJBBeanNameJndiPath(clazz, jndiNameBuilder, ejbType, ejb);
				try {
					return lookup(jndiNameBuilder, injectAnnotationWrapper, ejbType, context);
				} catch (NamingException e) {
					error.append("[beanName = ").append(jndiNameBuilder).append("]");
					jndiNameBuilder.delete(0, jndiNameBuilder.length());
				}
			}
			if (!"".equals(ejb.name())) {
				// 使用的是 name
				buildEJBNameJndiPath(clazz, jndiNameBuilder, ejbType, ejb);
				try {
					return lookup(jndiNameBuilder, injectAnnotationWrapper, ejbType, context);
				} catch (NamingException e) {
					error.append("[name = ").append(jndiNameBuilder).append("]");
					jndiNameBuilder.delete(0, jndiNameBuilder.length());
				}
			}
			// 什么都没指定则使用 默认ENC
			buildDefaultENCJndiPath(clazz, name, jndiNameBuilder);
			try {
				return lookup(jndiNameBuilder, injectAnnotationWrapper, ejbType, context);
			} catch (NamingException e) {
				error.append("[ENCJNDIName = ").append(jndiNameBuilder).append("]");
				jndiNameBuilder.delete(0, jndiNameBuilder.length());
			}
			try {
				return globalLookup(jndiNameBuilder, injectAnnotationWrapper, ejbType, context);
			} catch (NamingException e) {
				error.append("[defaultJNDIName = ").append(jndiNameBuilder).append("]").append("没有找到对应资源。");
				throw new NamingException(error.toString());
			}
		} finally {
			if (context != null) {
				context.close();
			}
		}
	}

	public Object fieldResourceAnnotationLookup(Class<?> clazz, FieldInjectAnnotationWrapper fieldInjectAnnotationWrapper) throws Exception {
		Field field = fieldInjectAnnotationWrapper.getField();
		Class<?> fieldType = field.getType();
		return lookup(clazz, fieldType, field.getName(), fieldInjectAnnotationWrapper,
				(Resource) fieldInjectAnnotationWrapper.getInjectAnnotation());
	}

	public Object methodResourceAnnotationLookup(Class<?> clazz, MethodInjectAnnotationWrapper methodInjectAnnotationWrapper)
			throws Exception {
		Method method = methodInjectAnnotationWrapper.getMethod();
		String fieldName = parseFieldName(method.getName());
		Class<?>[] parameterClasses = method.getParameterTypes();
		if (parameterClasses.length != 1) {
			throw new EJBException(method.getName() + " 不是写入方法。");
		}
		return lookup(clazz, parameterClasses[0], fieldName, methodInjectAnnotationWrapper,
				(Resource) methodInjectAnnotationWrapper.getInjectAnnotation());
	}

	/**
	 * 根据 resource 注解查找并返回对应的对象，并将查找正确对象的 jndi路径 调用
	 * {@link InjectAnnotationWrapper#setJndiPath(String)} 放入
	 * injectAnnotationWrapper 中缓存。
	 * 
	 * @param clazz 注解属于 字段/方法 的对象的 Class 对象。
	 * @param resourceType 注解注入类型的 Class 对象,如果是字段注入则为字段的 Class
	 *            ，如果为方法注入，则为写入方法的参数的 Class。
	 * @param name 注解注入 字段/方法 的参数名，如果为字段注入，则为字段名，如果为方法注入，则为写入方法的参数名。
	 * @param injectAnnotationWrapper 注入注解包装器。
	 * @param resource Resource注解。
	 * @return resouecr 注解所对应的对象。
	 * @throws Exception 发生任何异常时。
	 */
	protected Object lookup(Class<?> clazz, Class<?> resourceType, String name, InjectAnnotationWrapper injectAnnotationWrapper,
			Resource resource) throws Exception {
		StringBuilder jndiNameBuilder = new StringBuilder();
		StringBuilder error = new StringBuilder();
		Context context = null;
		try {
			context = new InitialContext();
			if (!"".equals(resource.mappedName())) {
				buildResourceMappedNameJndiPath(clazz, jndiNameBuilder, resourceType, resource);
				try {
					return lookup(jndiNameBuilder, injectAnnotationWrapper, resourceType, context);
				} catch (NamingException e) {
					error.append("[mappedName = ").append(jndiNameBuilder).append("]");
					jndiNameBuilder.delete(0, jndiNameBuilder.length());
				}
			}
			if (!"".equals(resource.name())) {
				// 使用的是 name
				buildResourceNameJndiPath(clazz, jndiNameBuilder, resourceType, resource);
				try {
					return lookup(jndiNameBuilder, injectAnnotationWrapper, resourceType, context);
				} catch (NamingException e) {
					error.append("[name = ").append(jndiNameBuilder).append("]");
					jndiNameBuilder.delete(0, jndiNameBuilder.length());
				}
			}
			// 什么都没指定则使用 默认ENC
			buildDefaultENCJndiPath(clazz, name, jndiNameBuilder);
			try {
				return lookup(jndiNameBuilder, injectAnnotationWrapper, resourceType, context);
			} catch (NamingException e) {
				error.append("[ENCJNDIName = ").append(jndiNameBuilder).append("]");
				jndiNameBuilder.delete(0, jndiNameBuilder.length());
			}
			try {
				return globalLookup(jndiNameBuilder, injectAnnotationWrapper, resourceType, context);
			} catch (NamingException e) {
				error.append("[defaultJNDIName = ").append(jndiNameBuilder).append("]").append("没有找到对应资源。");
				throw new NamingException(error.toString());
			}
		} finally {
			if (context != null) {
				context.close();
			}
		}
	}

	/**
	 * 查找资源，并将查找到的资源JNDI名缓存到injectAnnotationWrapper中。
	 * 
	 * @param jndiNameBuilder 当前的查找资源JNDI名，该参数是个 StringBuilder 。
	 * @param injectAnnotationWrapper 当前查找资源的 资源注入注解包装器。
	 * @param clazz 注解属于 字段/方法 的对象的 Class 对象。
	 * @param context 用于JNDI查找的Context。
	 * @return jndiNameBuilder 的 {@link StringBuilder#toString()} 对应的资源。
	 * @throws NamingException 命名异常时。
	 * @throws Exception 发生其他异常时。
	 * @see link InjectAnnotationWrapper#setJndiPath(String)
	 */
	protected Object lookup(StringBuilder jndiNameBuilder, InjectAnnotationWrapper injectAnnotationWrapper, Class<?> clazz, Context context)
			throws NamingException, Exception {
		String jndiPath = jndiNameBuilder.toString();
		Object resourceObject = context.lookup(jndiPath);
		injectAnnotationWrapper.setJndiPath(jndiPath);
		return resourceObject;
	}

	/**
	 * 构建默认的 ENC JNDI名。
	 * 
	 * @param clazz 注解属于 字段/方法 的对象的 Class 对象。
	 * @param name 注解注入 字段/方法 的参数名，如果为字段注入，则为字段名，如果为方法注入，则为写入方法的参数名。
	 * @param jndiNameBuilder 用于构建的查找资源JNDI名。
	 */
	protected void buildDefaultENCJndiPath(Class<?> clazz, String name, StringBuilder jndiNameBuilder) {
		jndiNameBuilder.append(ENC_PATH);
		jndiNameBuilder.append(clazz.getName());
		jndiNameBuilder.append(JNDI_PATH_DELIMITER);
		jndiNameBuilder.append(name);
	}

	/**
	 * 构建 {@link EJB#name()} 的 JNDI名。
	 * 
	 * @param clazz 注解属于 字段/方法 的对象的 Class 对象。
	 * @param jndiNameBuilder 用于构建的查找资源JNDI名。
	 * @param ejbType 注解注入类型的 Class 对象,如果是字段注入则为字段的 Class ，如果为方法注入，则为写入方法的参数的
	 *            Class。
	 * @param ejb EJB注解。
	 */
	protected abstract void buildEJBNameJndiPath(Class<?> clazz, StringBuilder jndiNameBuilder, Class<?> ejbType, EJB ejb);

	/**
	 * 构建 {@link EJB#beanName()} 的 JNDI名。
	 * 
	 * @param clazz 注解属于 字段/方法 的对象的 Class 对象。
	 * @param jndiNameBuilder 用于构建的查找资源JNDI名。
	 * @param ejbType 注解注入类型的 Class 对象,如果是字段注入则为字段的 Class ，如果为方法注入，则为写入方法的参数的
	 *            Class。
	 * @param ejb EJB注解。
	 */
	protected abstract void buildEJBBeanNameJndiPath(Class<?> clazz, StringBuilder jndiNameBuilder, Class<?> ejbType, EJB ejb);

	/**
	 * 构建 {@link EJB#mappedName()} 的 JNDI名。
	 * 
	 * @param clazz 注解属于 字段/方法 的对象的 Class 对象。
	 * @param jndiNameBuilder 用于构建的查找资源JNDI名。
	 * @param ejbType 注解注入类型的 Class 对象,如果是字段注入则为字段的 Class ，如果为方法注入，则为写入方法的参数的
	 *            Class。
	 * @param ejb EJB注解。
	 */
	protected abstract void buildEJBMappedNameJndiPath(Class<?> clazz, StringBuilder jndiNameBuilder, Class<?> ejbType, EJB ejb);

	/**
	 * 构建 {@link Resource#name()} 的 JNDI名。
	 * 
	 * @param clazz 注解属于 字段/方法 的对象的 Class 对象。
	 * @param jndiNameBuilder 用于构建的查找资源JNDI名。
	 * @param resourceType 注解注入类型的 Class 对象,如果是字段注入则为字段的 Class
	 *            ，如果为方法注入，则为写入方法的参数的 Class。
	 * @param resource Resource注解。
	 */
	protected abstract void buildResourceNameJndiPath(Class<?> clazz, StringBuilder jndiNameBuilder, Class<?> resourceType,
			Resource resource);

	/**
	 * 构建 {@link Resource#mappedName()} 的 JNDI名。
	 * 
	 * @param clazz 注解属于 字段/方法 的对象的 Class 对象。
	 * @param jndiNameBuilder 用于构建的查找资源JNDI名。
	 * @param resourceType 注解注入类型的 Class 对象,如果是字段注入则为字段的 Class
	 *            ，如果为方法注入，则为写入方法的参数的 Class。
	 * @param resource Resource注解。
	 */
	protected abstract void buildResourceMappedNameJndiPath(Class<?> clazz, StringBuilder jndiNameBuilder, Class<?> resourceType,
			Resource resource);

	private String parseFieldName(String methodName) {
		if (methodName.startsWith(ObjectUtils.SETTER)) {
			methodName = methodName.substring(ObjectUtils.SETTER.length());
			methodName = StringUtils.toLowerCase(methodName, 1);
		}
		return methodName;
	}

	/**
	 * 如果构造的 JNDI名 查找失败，则使用该方法构造默认的全局JNDI查找。
	 * 
	 * @param jndiNameBuilder 用于构建的查找资源JNDI名，当查找正确时，该参数会用来作为缓存到
	 *            InjectAnnotationWrapper 中。
	 * @param injectAnnotationWrapper 当前查找资源的 资源注入注解包装器。
	 * @param clazz 注解属于 字段/方法 的对象的 Class 对象。
	 * @param context 用于JNDI查找的Context。
	 * @return 对应的对象。
	 * @throws NamingException 命名异常时。
	 * @throws Exception 发生其他异常时。
	 */
	protected abstract Object globalLookup(StringBuilder jndiNameBuilder, InjectAnnotationWrapper injectAnnotationWrapper, Class<?> clazz,
			Context context) throws NamingException, Exception;

	/**
	 * 默认返回 null 。
	 */
	public Object fieldEJBComponentAnnotationLookup(Class<?> clazz, FieldInjectAnnotationWrapper fieldInjectAnnotationWrapper)
			throws Exception {
		return null;
	}

	/**
	 * 默认返回 null 。
	 */
	public Object methodEJBComponentAnnotationLookup(Class<?> clazz, MethodInjectAnnotationWrapper methodInjectAnnotationWrapper)
			throws Exception {
		return null;
	}

	/**
	 * 默认返回 null 。
	 */
	public Object fieldResourceComponentAnnotationLookup(Class<?> clazz, FieldInjectAnnotationWrapper fieldInjectAnnotationWrapper)
			throws Exception {
		return null;
	}

	/**
	 * 默认返回 null 。
	 */
	public Object methodResourceComponentAnnotationLookup(Class<?> clazz, MethodInjectAnnotationWrapper methodInjectAnnotationWrapper)
			throws Exception {
		return null;
	}

}